/**************************************************************
 * configfile.c - for simple config handling.
 * Copyright (c) 2009  Qin Xiaoxue  <crs.chin@gmail.com>
 *
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 * 
**************************************************************/


#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

#include "configfile.h"



static gchar *__get_basedir(const gchar *basedir)
{
  gchar *dir;

  if(basedir)  {
	if(! g_mkdir_with_parents(basedir, 0755))
	  return g_strdup(basedir);
	else
	  return NULL;
  }

  dir = (gchar *)g_getenv("HOME");
  if(dir)  {
	dir = g_strdup_printf("%s/%s/%s", dir, PROG_NAME, PROG_VERSION);
	if(g_mkdir_with_parents(dir, 0755))  {
	  g_free(dir);
	  return NULL;
	}
	return dir;
  }else  {
	dir = g_strdup_printf("/etc/%s/%s", PROG_NAME, PROG_VERSION);
	if(g_mkdir_with_parents(dir, 0755))  {
	  g_free(dir);
	  return NULL;
	}
	return dir;
  }
}


config_t *load_config_file(const gchar *file, const gchar *default_data, gint len, gboolean create);

config_t *load_config(const gchar *basedir, const gchar *category, 
					  const gchar *fname, const gchar *default_data,
					  gint len, gboolean create)
{
  config_t *config;
  gchar *dir, *path, *_basedir;
  GKeyFile *kfile;

  if(! fname)
	return NULL;

  _basedir = __get_basedir(basedir);
  if(! _basedir)
	return NULL;

  dir =  g_strdup_printf("%s/%s", _basedir, category ?: "");
  g_free(_basedir);
  if(g_mkdir_with_parents(dir, 0755))  {
	g_free(dir);
	return NULL;
  }

  path = g_strdup_printf("%s/%s", dir, fname);
  g_free(dir);
  config = load_config_file(path, default_data, len, create);
  g_free(path);

  return config;
}

static gint __file_size(const gchar *path)
{
  struct stat st;

  if(stat(path, &st))
	return 0;

  return st.st_size;
}

config_t *load_config_file(const gchar *file, const gchar *default_data, gint len, gboolean create)
{
  config_t *config;
  gchar *path, *fname;
  GKeyFile *kfile = NULL;
  FILE *fp = NULL;
  gboolean modified = FALSE;

  if(! file)
	return NULL;

  path = g_strdup(file);
  fname = strrchr(path, '/');
  if(! fname)
	fname = path;
  else
	fname++;

  kfile = g_key_file_new();
  if(g_file_test(path, G_FILE_TEST_IS_REGULAR)  ||	 __file_size(path) > 0 ) {
	if(! g_key_file_load_from_file(kfile, path, G_KEY_FILE_KEEP_COMMENTS, NULL))
	  print_warn("failed loading config from file %s, file may be corrupted.\n", path);
  }else  {
	if(! default_data && ! create)  {
	  g_key_file_free(kfile);
	  g_free(path);
	  return NULL;
	}

	if(default_data && (strlen(default_data) != 0))
	  if(! g_key_file_load_from_data(kfile, default_data, strlen(default_data),
									 G_KEY_FILE_KEEP_COMMENTS,  NULL))  {
		print_warn("Error loading config from data.\n");
		g_key_file_free(kfile);
		g_free(path);
		return NULL;
	  }

	if(create)  {
	  fp = fopen(path, "w");
	  if(! fp)
		print_err("Error open config file %s for writting.\n", path);
	  modified = TRUE;
	}
  }

  config = g_new0(config_t, 1);
  config->kfile = kfile;
  config->fp = fp;
  config->fname = strdup(fname);
  config->path = path;
  config->writeback = create;
  config->modified = modified;

  return config;
}


void     read_config(config_t *config, const gchar *group,
					 const gchar *key, gchar **val, 
					 const gchar *def, gboolean setback)
{
  const  gchar *_group;

  if(! val || ! key)
	return;

  if(! config)  {
	*val = NULL;
	return;
  }

  if(group)
	_group = group;
  else
	_group = "General";

  *val = g_key_file_get_string(config->kfile, _group, key, NULL);
  if(! *val)
	if(def)  {
	  *val = g_strdup(def);
	  if(setback)  {
		g_key_file_set_string(config->kfile, _group, key, def);
		config->modified = TRUE;
	  }
	}
}


void     set_config(config_t *config, const gchar *group,
					const gchar *key, gchar *val)
{
  if(! config || ! key || ! val)
	return;
  
  g_key_file_set_string(config->kfile, group ?: "General", key, val);
  config->modified = TRUE;
}


void sync_config(config_t *config)
{
  FILE *fp;
  gchar *buf;
  gsize len, i, j;

  if(! config || ! config->writeback)
	return;

  if(! config->writeback || ! config->modified)
	return;

  fp = config->fp;  
  if(! fp)  {
	fp = fopen(config->path, "w");
	if(! fp)  {
	  fprintf(stderr, "** ERROR: Error open config file %s for writting, aborted: %s.\n",
			  config->path, strerror(errno));
	  config->writeback = FALSE;
	  return;
	}
  }

  buf = g_key_file_to_data(config->kfile, &len, NULL);
  if(! buf)  {
	fprintf(stderr, "** WARNING: Failed to sync config to %s\n", config->path);
	return;
  }

  j = 0;
  do{
	i = fwrite(buf + j, 1, len, fp);
	j += i;
  }while(j < len);

  config->modified = FALSE;
}


gboolean sync_config_to(config_t *config, const gchar *file)
{
  FILE *fp;
  gchar *buf;
  gsize len, i, j;

  if(! config || ! file)
	return FALSE;

  fp = fopen(file, "w");
  if(! fp)  {
	fprintf(stderr, "** ERROR: Error open config file %s for writting, aborted: %s.\n",
			file, strerror(errno));
	return FALSE;
  }

  buf = g_key_file_to_data(config->kfile, &len, NULL);
  if(! buf)  {
	fprintf(stderr, "** WARNING: Failed to sync config to %s\n", config->path);
	return FALSE;
  }

  j = 0;
  do{
	i = fwrite(buf + j, 1, len, fp);
	j += i;
  }while(j < len);
  fclose(fp);

  return TRUE;
}


void    free_config(config_t *config)
{
  if(! config)
	return;

  if(config->writeback && config->modified)
	sync_config(config);

  if(config->kfile)
	g_key_file_free(config->kfile);
  if(config->fp)
	fclose(config->fp);
  if(config->fname)
	g_free((gpointer)config->fname);
  if(config->path)
	g_free((gpointer)config->path);
  g_free(config);
}


/***********************CONFIGFILE_TEST *******************************/


#ifdef CONFIGFILE_TEST

int main(int argc,char **argv)
{
  const gchar *basedir = "/home/qin/work/c/projects/libconfig";
  config_t *config;
  gchar *val;
  gint i;

  config = load_config(NULL, "category", "test.conf", NULL, 0, TRUE);
  if(! config)  {
	printf("** Failed loading config.\n");
	return 1;
  }

  read_config(config, NULL, "KeyOption", &val, "keyopenvalue", TRUE);
  printf("-- KeyOption value: %s\n", val);
  g_free(val);

  read_config(config, NULL, "KeyOption_a", &val, "keyopenvalue_another", FALSE);
  printf("-- KeyOption_a value: %s\n", val);
  g_free(val);

  set_config(config, "NewGroup", "NewOption", "1234");

  set_config_valtype(config, comment, "NewGroup", "NewOption", "A test of a integer key pair");

  read_config_valtype(config, i, integer, "NewGroup", "NewOption");

  printf("New option's integer value: %d\n", i);

  printf("config file path: %s\n", config->path);
  
  free_config(config);

  return 0;
}

#endif
